﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text.RegularExpressions;
using System.Data.SQLite;
using System.Threading.Tasks;
using System.Data;

namespace ConsoleApp
{
    public class MBTilesConvert
    {
        /// <summary>
        /// 转换瓦片目录到MBTiles（支持层级筛选、批量插入优化）
        /// </summary>
        /// <param name="tilesDirectory">瓦片根目录</param>
        /// <param name="mbtilesPath">输出MBTiles文件路径</param>
        /// <param name="tileFormat">瓦片格式（png/jpg）</param>
        /// <param name="isTmsFormat">是否为TMS坐标格式（需转为XYZ）</param>
        /// <param name="metadata">自定义元数据</param>
        /// <param name="progressCallback">进度回调（已处理数/总数）</param>
        /// <param name="minZoomLevel">最小处理层级（如15）</param>
        /// <param name="maxZoomLevel">最大处理层级（如22）</param>
        /// <param name="batchSize">每批插入瓦片数量（建议1000-50000）</param>
        public static async Task Convert(string tilesDirectory, string mbtilesPath,
                                        string tileFormat = "png", bool isTmsFormat = true,
                                        Dictionary<string, string> metadata = null,
                                        Action<int, int> progressCallback = null,
                                        int? minZoomLevel = null,
                                        int? maxZoomLevel = null,
                                        int batchSize = 10000)
        {
            // 1. 参数验证
            if (!Directory.Exists(tilesDirectory))
                throw new DirectoryNotFoundException($"瓦片目录不存在: {tilesDirectory}");

            if (string.IsNullOrEmpty(mbtilesPath))
                throw new ArgumentNullException(nameof(mbtilesPath), "输出MBTiles文件路径不能为空");

            if (tileFormat != "png" && tileFormat != "jpg")
                throw new ArgumentException("瓦片格式必须是png或jpg", nameof(tileFormat));

            if (minZoomLevel.HasValue && maxZoomLevel.HasValue && minZoomLevel > maxZoomLevel)
                throw new ArgumentException("最小层级不能大于最大层级", nameof(minZoomLevel));

            // 2. 准备输出目录
            var outputDir = Path.GetDirectoryName(mbtilesPath);
            if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir))
                Directory.CreateDirectory(outputDir);

            // 3. 删除已存在的MBTiles文件（避免冲突）
            if (File.Exists(mbtilesPath))
                File.Delete(mbtilesPath);

            // 4. 扫描并筛选瓦片文件（仅保留指定层级）
            var allTileFiles = GetAllTileFiles(tilesDirectory, tileFormat);
            var filteredFiles = FilterTileFilesByZoomLevel(allTileFiles, tilesDirectory, minZoomLevel, maxZoomLevel);
            int totalTiles = filteredFiles.Count;
            int processedTiles = 0;

            if (totalTiles == 0)
                throw new InvalidOperationException("未找到符合层级条件的瓦片文件");

            // 5. 数据库核心操作
            using (var connection = new SQLiteConnection(
                $"Data Source={mbtilesPath};Version=3;Cache Size=-20000;")) // 20MB缓存优化写入性能
            {
                connection.Open();
                // 初始化数据库表结构+元数据（包含层级信息）
                InitializeDatabase(connection, tileFormat, metadata, minZoomLevel, maxZoomLevel);

                // 6. 批量插入配置
                // 限制批量大小避免内存溢出
                if (batchSize < 1000)
                    batchSize = 1000; // 最小批量1000
                else if (batchSize > 50000)
                    batchSize = 50000; // 最大批量50000
                SQLiteCommand insertCommand = null;
                SQLiteTransaction transaction = null;

                try
                {
                    // 7. 准备参数化SQL命令（复用避免重复创建开销）
                    insertCommand = new SQLiteCommand(@"
                        INSERT OR REPLACE INTO tiles 
                        (zoom_level, tile_column, tile_row, tile_data) 
                        VALUES (@z, @x, @y, @data)", connection);

                    // 预定义参数（避免循环中重复创建）
                    var paramZ = new SQLiteParameter("@z", DbType.Int32);
                    var paramX = new SQLiteParameter("@x", DbType.Int32);
                    var paramY = new SQLiteParameter("@y", DbType.Int32);
                    var paramData = new SQLiteParameter("@data", DbType.Binary);
                    insertCommand.Parameters.AddRange(new[] { paramZ, paramX, paramY, paramData });

                    // 8. 初始化第一个事务
                    transaction = connection.BeginTransaction();
                    int currentBatchCount = 0;

                    // 9. 遍历处理瓦片
                    foreach (var tileFile in filteredFiles)
                    {
                        try
                        {
                            // 解析瓦片坐标（z/x/y）
                            if (!ParseTilePath(tileFile, tilesDirectory, out int z, out int x, out int y) ||
                                !IsValidTileCoordinate(z, x, y))
                            {
                                Console.WriteLine($"跳过无效瓦片：{tileFile}");
                                continue;
                            }

                            // 10. TMS→XYZ坐标转换（若需要）
                            if (isTmsFormat)
                                y = (1 << z) - 1 - y; // 2^z -1 计算当前层级最大y值

                            // 11. 读取瓦片二进制数据
                            byte[] tileData = await ReadTileData(tileFile, tileFormat);

                            // 12. 赋值参数并执行插入
                            paramZ.Value = z;
                            paramX.Value = x;
                            paramY.Value = y;
                            paramData.Value = tileData;
                            insertCommand.ExecuteNonQuery();

                            // 13. 批量计数+进度更新
                            currentBatchCount++;
                            processedTiles++;
                            progressCallback?.Invoke(processedTiles, totalTiles);

                            // 14. 达到批量大小：提交当前事务→创建新事务
                            if (currentBatchCount >= batchSize)
                            {
                                transaction.Commit();   // 提交批次数据
                                transaction.Dispose();  // 释放旧事务资源
                                transaction = connection.BeginTransaction(); // 新建事务
                                currentBatchCount = 0;  // 重置批量计数
                            }
                        }
                        catch (Exception ex)
                        {
                            // 单个瓦片处理失败不中断整体流程
                            Console.WriteLine($"处理瓦片 {tileFile} 失败：{ex.Message}");
                        }
                    }

                    // 15. 提交最后一批剩余数据
                    if (transaction != null && transaction.Connection != null)
                        transaction.Commit();

                    Console.WriteLine($"\n转换完成！共处理 {processedTiles}/{totalTiles} 个瓦片");
                }
                catch (Exception ex)
                {
                    // 16. 异常回滚，避免数据损坏
                    if (transaction != null && transaction.Connection != null)
                    {
                        transaction.Rollback();
                        Console.WriteLine($"事务执行失败，已回滚：{ex.Message}");
                    }
                    throw; // 重新抛出异常供上层处理
                }
                finally
                {
                    // 17. 释放资源（确保无论成功/失败都执行）
                    transaction?.Dispose();
                    insertCommand?.Dispose();
                }
            }

            // 最终进度回调（100%）
            progressCallback?.Invoke(totalTiles, totalTiles);
        }

        /// <summary>
        /// 根据层级范围筛选瓦片文件（核心：提前过滤无效层级，减少后续IO）
        /// </summary>
        private static List<string> FilterTileFilesByZoomLevel(List<string> allFiles, string baseDir,
                                                            int? minZoom, int? maxZoom)
        {
            if (!minZoom.HasValue && !maxZoom.HasValue)
                return allFiles;

            var filtered = new List<string>();
            foreach (var file in allFiles)
            {
                try
                {
                    // 仅提取路径中的层级（z），避免完整解析x/y提升效率
                    string relativePath = GetRelativePath(baseDir, file);
                    var zoomMatch = Regex.Match(relativePath, @"^(?<z>\d+)"); // 匹配开头的数字（层级）

                    if (zoomMatch.Success && int.TryParse(zoomMatch.Groups["z"].Value, out int z))
                    {
                        // 检查是否在目标层级范围内
                        bool isInRange = true;
                        if (minZoom.HasValue && z < minZoom.Value) isInRange = false;
                        if (maxZoom.HasValue && z > maxZoom.Value) isInRange = false;

                        if (isInRange)
                            filtered.Add(file);
                    }
                }
                catch
                {
                    // 路径解析失败的文件直接跳过
                    continue;
                }
            }
            return filtered;
        }

        /// <summary>
        /// 初始化MBTiles数据库（符合1.3规范，包含层级元数据）
        /// </summary>
        private static void InitializeDatabase(SQLiteConnection connection, string tileFormat,
                                              Dictionary<string, string> customMetadata,
                                              int? minZoom, int? maxZoom)
        {
            // 1. 创建元数据表（存储地图信息）
            ExecuteNonQuery(connection, @"
                CREATE TABLE metadata (
                    name TEXT PRIMARY KEY,
                    value TEXT
                )");

            // 2. 创建瓦片数据表（主键确保坐标唯一）
            ExecuteNonQuery(connection, @"
                CREATE TABLE tiles (
                    zoom_level INTEGER,
                    tile_column INTEGER,
                    tile_row INTEGER,
                    tile_data BLOB,
                    PRIMARY KEY (zoom_level, tile_column, tile_row)
                )");

            // 3. 创建索引（加速瓦片查询，尤其是按层级筛选时）
            ExecuteNonQuery(connection, @"
                CREATE INDEX idx_tiles_zoom ON tiles (zoom_level, tile_column, tile_row)");

            // 4. 默认元数据（符合MBTiles规范）
            SetMetadata(connection, "version", "1.3");
            SetMetadata(connection, "format", tileFormat);
            SetMetadata(connection, "type", "baselayer");
            SetMetadata(connection, "generator", "Custom MBTiles Converter");

            // 5. 添加层级元数据（若指定）
            if (minZoom.HasValue)
                SetMetadata(connection, "minzoom", minZoom.Value.ToString());
            if (maxZoom.HasValue)
                SetMetadata(connection, "maxzoom", maxZoom.Value.ToString());

            // 6. 合并自定义元数据（如地图名称、版权信息等）
            if (customMetadata != null)
            {
                foreach (var kv in customMetadata)
                {
                    SetMetadata(connection, kv.Key, kv.Value);
                }
            }
        }

        /// <summary>
        /// 写入/更新元数据
        /// </summary>
        private static void SetMetadata(SQLiteConnection connection, string name, string value)
        {
            ExecuteNonQuery(connection,
                "INSERT OR REPLACE INTO metadata (name, value) VALUES (@name, @value)",
                new SQLiteParameter("@name", name),
                new SQLiteParameter("@value", value)
            );
        }

        /// <summary>
        /// 执行无返回值SQL（复用方法）
        /// </summary>
        private static void ExecuteNonQuery(SQLiteConnection connection, string sql,
                                          params SQLiteParameter[] parameters)
        {
            using (var cmd = new SQLiteCommand(sql, connection))
            {
                if (parameters != null && parameters.Length > 0)
                    cmd.Parameters.AddRange(parameters);
                cmd.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// 解析瓦片路径获取z/x/y坐标（支持Windows/Linux路径分隔符）
        /// </summary>
        private static bool ParseTilePath(string filePath, string baseDir,
                                        out int z, out int x, out int y)
        {
            z = x = y = 0;
            try
            {
                string relativePath = GetRelativePath(baseDir, filePath);
                // 匹配格式：z/x/y.png 或 z\x\y.jpg（支持正反斜杠）
                var match = Regex.Match(relativePath, @"^(?<z>\d+)[\\/](?<x>\d+)[\\/](?<y>\d+)\.(png|jpg)$",
                    RegexOptions.IgnoreCase);

                if (match.Success)
                {
                    z = int.Parse(match.Groups["z"].Value);
                    x = int.Parse(match.Groups["x"].Value);
                    y = int.Parse(match.Groups["y"].Value);
                    return true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"解析路径 {filePath} 失败：{ex.Message}");
            }
            return false;
        }

        /// <summary>
        /// 自定义相对路径计算（兼容.NET Framework旧版本，无需依赖Path.GetRelativePath）
        /// </summary>
        private static string GetRelativePath(string basePath, string targetPath)
        {
            // 标准化路径（统一为全路径，处理.././等相对符号）
            string normalizedBase = Path.GetFullPath(basePath).TrimEnd(Path.DirectorySeparatorChar);
            string normalizedTarget = Path.GetFullPath(targetPath).TrimEnd(Path.DirectorySeparatorChar);

            // 不同根目录（如C盘/D盘）直接返回绝对路径
            string baseRoot = Path.GetPathRoot(normalizedBase);
            string targetRoot = Path.GetPathRoot(normalizedTarget);
            if (!string.Equals(baseRoot, targetRoot, StringComparison.OrdinalIgnoreCase))
                return normalizedTarget;

            // 拆分路径为组件（如["C:", "tiles", "15", "345"]）
            string[] baseParts = normalizedBase.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar },
                StringSplitOptions.RemoveEmptyEntries);
            string[] targetParts = normalizedTarget.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar },
                StringSplitOptions.RemoveEmptyEntries);

            // 找到共同路径前缀
            int commonPrefixLength = 0;
            while (commonPrefixLength < baseParts.Length && commonPrefixLength < targetParts.Length &&
                   string.Equals(baseParts[commonPrefixLength], targetParts[commonPrefixLength], StringComparison.OrdinalIgnoreCase))
            {
                commonPrefixLength++;
            }

            // 计算需要回退的层级（如../../）
            List<string> relativeParts = new List<string>();
            for (int i = commonPrefixLength; i < baseParts.Length; i++)
            {
                relativeParts.Add("..");
            }

            // 添加目标路径的独有部分
            for (int i = commonPrefixLength; i < targetParts.Length; i++)
            {
                relativeParts.Add(targetParts[i]);
            }

            // 组合为相对路径
            return Path.Combine(relativeParts.ToArray());
        }

        /// <summary>
        /// 递归扫描目录下所有瓦片文件（含异常处理，避免单个目录错误中断整体扫描）
        /// </summary>
        private static List<string> GetAllTileFiles(string directory, string format)
        {
            var tileFiles = new List<string>();
            string searchPattern = $"*.{format.ToLower()}";

            try
            {
                // 广度优先扫描（避免深度过深导致栈溢出）
                var dirQueue = new Queue<string>();
                dirQueue.Enqueue(directory);

                while (dirQueue.Count > 0)
                {
                    string currentDir = dirQueue.Dequeue();
                    try
                    {
                        // 添加当前目录的瓦片文件
                        tileFiles.AddRange(Directory.EnumerateFiles(currentDir, searchPattern, SearchOption.TopDirectoryOnly));

                        // 入队子目录（继续扫描）
                        foreach (var subDir in Directory.EnumerateDirectories(currentDir, "*", SearchOption.TopDirectoryOnly))
                        {
                            dirQueue.Enqueue(subDir);
                        }
                    }
                    catch (UnauthorizedAccessException ex)
                    {
                        Console.WriteLine($"无权访问目录 {currentDir}：{ex.Message}（已跳过）");
                    }
                    catch (PathTooLongException ex)
                    {
                        Console.WriteLine($"目录路径过长 {currentDir}：{ex.Message}（已跳过）");
                    }
                    catch (IOException ex)
                    {
                        Console.WriteLine($"IO错误 {currentDir}：{ex.Message}（已跳过）");
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"扫描瓦片目录失败：{ex.Message}");
            }

            return tileFiles;
        }

        /// <summary>
        /// 读取瓦片数据（支持格式自动转换，如jpg转png）
        /// </summary>
        private static async Task<byte[]> ReadTileData(string filePath, string targetFormat)
        {
            string fileExt = Path.GetExtension(filePath).TrimStart('.').ToLower();
            // 格式一致：直接读取字节（性能最优）
            if (fileExt == targetFormat.ToLower())
                return await ReadAllBytesAsync(filePath);

            // 格式不一致：转换为目标格式（如jpg→png）
            using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
            using (var image = Image.FromStream(fileStream))
            using (var outputStream = new MemoryStream())
            {
                ImageFormat format = targetFormat.Equals("png", StringComparison.OrdinalIgnoreCase)
                    ? ImageFormat.Png : ImageFormat.Jpeg;
                image.Save(outputStream, format);
                return outputStream.ToArray();
            }
        }

        /// <summary>
        /// 异步读取文件字节（兼容.NET Framework旧版本，无需依赖File.ReadAllBytesAsync）
        /// </summary>
        private static async Task<byte[]> ReadAllBytesAsync(string filePath)
        {
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
            {
                byte[] buffer = new byte[stream.Length];
                int totalRead = 0;
                // 异步读取所有字节（避免一次性加载导致内存峰值）
                while (totalRead < buffer.Length)
                {
                    int readCount = await stream.ReadAsync(buffer, totalRead, buffer.Length - totalRead);
                    if (readCount == 0)
                        break; // 意外结束（理论上不会触发）
                    totalRead += readCount;
                }
                return buffer;
            }
        }

        /// <summary>
        /// 验证瓦片坐标有效性（确保x/y不超过当前层级的最大范围）
        /// </summary>
        private static bool IsValidTileCoordinate(int z, int x, int y)
        {
            if (z < 0) return false;
            int maxCoord = (1 << z) - 1; // 2^z - 1（当前层级最大x/y值）
            return x >= 0 && x <= maxCoord && y >= 0 && y <= maxCoord;
        }
    }
}